﻿using Sidvall.Serialization;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Sidvall.IO
{
    public class FileSystem : IStorage
    {
        #region Public Members

        #region DefaultBufferSize

        public int DefaultBufferSize
        {
            get
            {
                return 4096;
            }
        }

        #endregion
        #region FolderDelimiter

        public string FolderDelimiter
        {
            get
            {
                return System.IO.Path.DirectorySeparatorChar.ToString();
            }
        }

        #endregion
        #region RootPath

        private string _RootPath;
        public string RootPath
        {
            get
            {
                return _RootPath;
            }
            set
            {
                _RootPath = value;
            }
        }

        #endregion

        #region Combine

        public string Combine(params string[] paths)
        {
            return System.IO.Path.Combine(paths).Replace(new string[] { "\\", "/" }, this.FolderDelimiter, System.StringComparison.Ordinal);
        }

        #endregion
        #region GetFolderPath

        public string GetFolderPath(FolderType folder)
        {
            switch (folder)
            {
                case Sidvall.IO.FolderType.InstalledLocation:
                    return System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
                case Sidvall.IO.FolderType.LocalApplicationData:
                    return System.Windows.Forms.Application.LocalUserAppDataPath;
                case Sidvall.IO.FolderType.RoamingApplicationData:
                    return System.Windows.Forms.Application.UserAppDataPath;
                case Sidvall.IO.FolderType.TemporaryApplicationData:
                    return System.IO.Path.GetTempPath();
                case Sidvall.IO.FolderType.Desktop:
                    return System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop);
                case Sidvall.IO.FolderType.MyDocuments:
                    return System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
                case Sidvall.IO.FolderType.MyMusic:
                    return System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyMusic);
                case Sidvall.IO.FolderType.MyPictures:
                    return System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyPictures);
                case Sidvall.IO.FolderType.MyVideos:
                    return System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyVideos);
                default:
                    throw new System.ArgumentException(nameof(folder));
            }
        }

        #endregion
        #region GetPath

        public string GetPath(string path)
        {
            if (path != null)
                path = path.Replace(new string[] { "\\", "/" }, this.FolderDelimiter, System.StringComparison.Ordinal);
            if (string.IsNullOrWhiteSpace(this.RootPath))
                return path;
            if (string.IsNullOrWhiteSpace(path))
                return this.RootPath;
            if (System.IO.Path.IsPathRooted(path))
                return path;
            return Combine(this.RootPath, path);
        }

        #endregion
        #region GetStoragePath

        public string GetStoragePath(string path)
        {
            if (path != null)
                path = path.Replace(new string[] { "\\", "/" }, this.FolderDelimiter, System.StringComparison.Ordinal);
            return Sidvall.IO.StorageGroupManager.GetPathWithoutRoot(path, this.RootPath, this.FolderDelimiter);
        }

        #endregion

        #region CreateFolderAsync

        public Task<bool> CreateFolderAsync(string path, CancellationToken cancellationToken)
        {
            path = GetPath(path);
            System.IO.Directory.CreateDirectory(path);

            return Task.FromResult(true);
        }

        #endregion

        #region DeleteFileAsync

        public Task<bool> DeleteFileAsync(string path, CancellationToken cancellationToken)
        {
            path = GetPath(path);
            System.IO.File.Delete(path);

            return Task.FromResult(true);
        }

        #endregion
        #region DeleteFolderAsync

        public Task<bool> DeleteFolderAsync(string path, bool recursive, CancellationToken cancellationToken)
        {
            path = GetPath(path);
            System.IO.Directory.Delete(path, recursive);

            return Task.FromResult(true);
        }

        #endregion

        #region FileExistsAsync

        public Task<bool> FileExistsAsync(string path, CancellationToken cancellationToken)
        {
            path = GetPath(path);
            var result = System.IO.File.Exists(path);

            return Task.FromResult(result);
        }

        #endregion
        #region FolderExistsAsync

        public Task<bool> FolderExistsAsync(string path, CancellationToken cancellationToken)
        {
            path = GetPath(path);
            var result = System.IO.Directory.Exists(path);

            return Task.FromResult(result);
        }

        #endregion

        #region GetFileNamesAsync

        public Task<IEnumerable<string>> GetFileNamesAsync(string path, string searchPattern, SearchOption searchOption,
            CancellationToken cancellationToken)
        {
            path = GetPath(path);
            switch (searchOption)
            {
                case SearchOption.AllDirectoriesIgnoreSecurityExceptions:
                    var items = new List<string>();
                    ProcessFolder(items, path, searchPattern, true, false, cancellationToken);
                    return Task.FromResult(items.AsEnumerable());
                default:
                    var enumItems = System.IO.Directory.EnumerateFiles(path, searchPattern, GetSearchOption(searchOption));
                    return Task.FromResult(enumItems);
            }
        }

        #endregion
        #region GetFolderNamesAsync

        public Task<IEnumerable<string>> GetFolderNamesAsync(string path, string searchPattern, SearchOption searchOption,
            CancellationToken cancellationToken)
        {
            path = GetPath(path);
            switch (searchOption)
            {
                case SearchOption.AllDirectoriesIgnoreSecurityExceptions:
                    var items = new List<string>();
                    ProcessFolder(items, path, searchPattern, false, true, cancellationToken);
                    return Task.FromResult(items.AsEnumerable());
                default:
                    var enumItems = System.IO.Directory.EnumerateDirectories(path, searchPattern, GetSearchOption(searchOption));
                    return Task.FromResult(enumItems);
            }
        }

        #endregion
        #region GetFileSystemEntryNamesAsync

        public Task<IEnumerable<string>> GetFileSystemEntryNamesAsync(string path, string searchPattern, SearchOption searchOption,
            CancellationToken cancellationToken)
        {
            path = GetPath(path);
            switch (searchOption)
            {
                case SearchOption.AllDirectoriesIgnoreSecurityExceptions:
                    var items = new List<string>();
                    ProcessFolder(items, path, searchPattern, true, true, cancellationToken);
                    return Task.FromResult(items.AsEnumerable());
                default:
                    var enumItems = System.IO.Directory.EnumerateFileSystemEntries(path, searchPattern, GetSearchOption(searchOption));
                    return Task.FromResult(enumItems);
            }
        }

        #endregion

        #region GetFileAsync

        public Task<File> GetFileAsync(string fileName, bool loadFileDetails, CancellationToken cancellationToken)
        {
            fileName = GetPath(fileName);
            var item = GetFile(fileName, loadFileDetails);
            return Task.FromResult(item);
        }

        #endregion
        #region GetFilesAsync

        public Task<IEnumerable<File>> GetFilesAsync(string path, string searchPattern, SearchOption searchOption, bool loadFileDetails,
            CancellationToken cancellationToken)
        {
            var items = new List<File>();
            path = GetPath(path);
            switch (searchOption)
            {
                case SearchOption.AllDirectoriesIgnoreSecurityExceptions:
                    ProcessFolder(items, path, searchPattern, loadFileDetails, cancellationToken);
                    return Task.FromResult(items.AsEnumerable());
                default:
                    ImportFiles(items, System.IO.Directory.EnumerateFiles(path, searchPattern, GetSearchOption(searchOption)), loadFileDetails);
                    return Task.FromResult(items.AsEnumerable());
            }
        }

        #endregion
        #region GetFolderAsync

        public Task<Folder> GetFolderAsync(string folderName, bool loadFolderDetails, CancellationToken cancellationToken)
        {
            folderName = GetPath(folderName);
            var item = GetFolder(folderName, loadFolderDetails);
            return Task.FromResult(item);
        }

        #endregion
        #region GetFoldersAsync

        public Task<IEnumerable<Folder>> GetFoldersAsync(string path, string searchPattern, SearchOption searchOption, bool loadFolderDetails,
            CancellationToken cancellationToken)
        {
            var items = new List<Folder>();
            path = GetPath(path);
            switch (searchOption)
            {
                case SearchOption.AllDirectoriesIgnoreSecurityExceptions:
                    ProcessFolder(items, path, searchPattern, loadFolderDetails, cancellationToken);
                    return Task.FromResult(items.AsEnumerable());
                default:
                    ImportFolders(items, System.IO.Directory.EnumerateDirectories(path, searchPattern, GetSearchOption(searchOption)), loadFolderDetails);
                    return Task.FromResult(items.AsEnumerable());
            }
        }

        #endregion

        #region OpenFileAsync

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        public Task<System.IO.Stream> OpenFileAsync(string path, FileMode mode, FileAccess access, CancellationToken cancellationToken)
        {
            System.IO.Stream result;

            path = GetPath(path);
            result = System.IO.File.Open(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(access));

            return Task.FromResult(result);
        }

        #endregion

        #region CopyFileAsync

        public Task<bool> CopyFileAsync(string sourceFileName, string destinationFileName, bool deleteExistingFile, CancellationToken cancellationToken)
        {
            sourceFileName = GetPath(sourceFileName);
            destinationFileName = GetPath(destinationFileName);
            System.IO.File.Copy(sourceFileName, destinationFileName, deleteExistingFile);

            return Task.FromResult(true);
        }

        #endregion
        #region MoveFileAsync

        public Task<bool> MoveFileAsync(string sourceFileName, string destinationFileName, bool deleteExistingFile, CancellationToken cancellationToken)
        {
            sourceFileName = GetPath(sourceFileName);
            destinationFileName = GetPath(destinationFileName);
            if (deleteExistingFile)
            {
                if (System.IO.File.Exists(destinationFileName))
                    System.IO.File.Delete(destinationFileName);
            }
            if (System.IO.File.Exists(destinationFileName))
                return Task.FromResult(false);
            System.IO.File.Move(sourceFileName, destinationFileName);

            return Task.FromResult(true);
        }

        #endregion
        #region MoveFolderAsync

        public Task<bool> MoveFolderAsync(string sourceFolderName, string destinationFolderName, bool deleteExistingFolder, CancellationToken cancellationToken)
        {
            string path;

            sourceFolderName = GetPath(sourceFolderName);
            destinationFolderName = GetPath(destinationFolderName);
            path = System.IO.Path.GetDirectoryName(destinationFolderName);
            if (deleteExistingFolder)
            {
                if (System.IO.Directory.Exists(destinationFolderName))
                    System.IO.Directory.Delete(destinationFolderName);
            }
            if (System.IO.File.Exists(destinationFolderName))
                return Task.FromResult(false);
            if (!System.IO.Directory.Exists(path))
                System.IO.Directory.CreateDirectory(path);
            System.IO.Directory.Move(sourceFolderName, destinationFolderName);

            return Task.FromResult(true);
        }

        #endregion

        #region ToString

        public override string ToString()
        {
            return "FileSystem - System.IO";
        }

        #endregion

        #endregion
        #region Private Members

        #region GetFile

        private File GetFile(string path, bool loadDetails)
        {
            var item = new File()
            {
                FullName = path,
                StorageName = GetStoragePath(path),
                Name = System.IO.Path.GetFileName(path),
            };
            if (loadDetails)
            {
                var file = new System.IO.FileInfo(path);
                if (!file.Exists)
                    return null;
                item.CreationTime = file.CreationTimeUtc;
                item.LastWriteTime = file.LastWriteTimeUtc;
                item.Size = file.Length;
            }
            return item;
        }

        #endregion
        #region GetFileAccess

        private static System.IO.FileAccess GetFileAccess(FileAccess access)
        {
            switch (access)
            {
                case FileAccess.Write:
                    return System.IO.FileAccess.Write;
                case FileAccess.ReadWrite:
                    return System.IO.FileAccess.ReadWrite;
                default:
                    return System.IO.FileAccess.Read;
            }
        }

        #endregion
        #region GetFileMode

        private static System.IO.FileMode GetFileMode(FileMode mode)
        {
            switch (mode)
            {
                case FileMode.CreateNew:
                    return System.IO.FileMode.CreateNew;
                case FileMode.Create:
                    return System.IO.FileMode.Create;
                case FileMode.OpenOrCreate:
                    return System.IO.FileMode.OpenOrCreate;
                case FileMode.Truncate:
                    return System.IO.FileMode.Truncate;
                case FileMode.Append:
                    return System.IO.FileMode.Append;
                default:
                    return System.IO.FileMode.Open;
            }
        }

        #endregion
        #region GetFileShare

        private static System.IO.FileShare GetFileShare(FileAccess access)
        {
            switch (access)
            {
                case FileAccess.Write:
                    return System.IO.FileShare.None;
                case FileAccess.ReadWrite:
                    return System.IO.FileShare.None;
                default:
                    return System.IO.FileShare.Read;
            }
        }

        #endregion
        #region GetFolder

        private Folder GetFolder(string path, bool loadDetails)
        {
            var item = new Folder()
            {
                FullName = path,
                StorageName = GetStoragePath(path),
                Name = System.IO.Path.GetFileName(path),
            };
            if (loadDetails)
            {
                var folder = new System.IO.DirectoryInfo(path);
                if (!folder.Exists)
                    return null;
                item.CreationTime = folder.CreationTimeUtc;
                item.LastWriteTime = folder.LastWriteTimeUtc;
            }
            return item;
        }

        #endregion
        #region GetSearchOption

        private static System.IO.SearchOption GetSearchOption(SearchOption searchOption)
        {
            switch (searchOption)
            {
                case SearchOption.AllDirectories:
                    return System.IO.SearchOption.AllDirectories;
                case SearchOption.TopDirectoryOnly:
                    return System.IO.SearchOption.TopDirectoryOnly;
                case SearchOption.AllDirectoriesIgnoreSecurityExceptions:
                    return System.IO.SearchOption.TopDirectoryOnly;
                default:
                    throw new System.ArgumentException("Invalid searchOption");
            }
        }

        #endregion
        #region ImportFiles

        private void ImportFiles(List<File> items, IEnumerable<string> paths, bool loadDetails)
        {
            foreach (var path in paths)
            {
                var item = GetFile(path, loadDetails);
                if (item != null)
                    items.Add(item);
            }
        }

        #endregion
        #region ImportFolders

        private void ImportFolders(List<Folder> items, IEnumerable<string> paths, bool loadDetails)
        {
            foreach (var path in paths)
            {
                var item = GetFolder(path, loadDetails);
                if (item != null)
                    items.Add(item);
            }
        }

        #endregion
        #region ProcessFolder

        private static void ProcessFolder(List<string> items, string path, string searchPattern, bool includeFiles, bool includeFolders,
            CancellationToken cancellationToken)
        {
            IEnumerable<string> folders;

            cancellationToken.ThrowIfCancellationRequested();
            try
            {
                if (includeFiles)
                {
                    var files = System.IO.Directory.EnumerateFiles(path, searchPattern, System.IO.SearchOption.TopDirectoryOnly);
                    foreach (var file in files)
                        items.Add(file);
                }
                if (includeFolders)
                {
                    folders = System.IO.Directory.EnumerateDirectories(path, searchPattern, System.IO.SearchOption.TopDirectoryOnly);
                    foreach (var folder in folders)
                        items.Add(folder);
                }
                else
                    folders = System.IO.Directory.EnumerateDirectories(path, "*", System.IO.SearchOption.TopDirectoryOnly);
                foreach (var folder in folders)
                    ProcessFolder(items, folder, searchPattern, includeFiles, includeFolders, cancellationToken);
            }
            catch (System.UnauthorizedAccessException)
            {
            }
        }
        private void ProcessFolder(List<File> items, string path, string searchPattern, bool loadDetails,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            try
            {
                var paths = System.IO.Directory.EnumerateFiles(path, searchPattern, System.IO.SearchOption.TopDirectoryOnly);
                ImportFiles(items, paths, loadDetails);
                paths = System.IO.Directory.EnumerateDirectories(path, "*", System.IO.SearchOption.TopDirectoryOnly);
                foreach (var subFolder in paths)
                    ProcessFolder(items, subFolder, searchPattern, loadDetails, cancellationToken);
            }
            catch (System.UnauthorizedAccessException)
            {
            }
        }
        private void ProcessFolder(List<Folder> items, string path, string searchPattern, bool loadDetails,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            try
            {
                var paths = System.IO.Directory.EnumerateDirectories(path, searchPattern, System.IO.SearchOption.TopDirectoryOnly);
                ImportFolders(items, paths, loadDetails);
                paths = System.IO.Directory.EnumerateDirectories(path, "*", System.IO.SearchOption.TopDirectoryOnly);
                foreach (var subFolder in paths)
                    ProcessFolder(items, subFolder, searchPattern, loadDetails, cancellationToken);
            }
            catch (System.UnauthorizedAccessException)
            {
            }
        }
        #endregion

        #endregion
        #region Constructors

        public FileSystem()
        {
        }
        public FileSystem(FolderType folder)
        {
            this.RootPath = GetFolderPath(folder);
        }
        public FileSystem(string rootPath)
        {
            this.RootPath = rootPath;
        }

        #endregion
    }
}
